/* 
 * Interactive 3D - 2012
 * File Name: main.c
 * Author: s3298400 - To Bao Thien Quan
 */

#include "main.h"

Global global;
Color water_color = { 0.2, 0.2, 0.9 };
Color red = { 1, 0, 0 };
Color green = { 0, 1, 0 };
Color blue = { 0, 0, 1 };
Color yellow = { 1, 1, 0 };
Color white = { 1, 1, 1 };
Color pink = { 1, 0, 1 };
Color brown = { 0.55, 0.22, 0.05 };

int main(int argc, char **argv){
	initialize_glut_and_global_variables(argc, argv);
	create_window();
	openGL_settings();
	set_callbacks();
	glutMainLoop();
	//Never reached
	return EXIT_SUCCESS;
}
void initialize_glut_and_global_variables(int argc, char **argv){

	//Initialize glut
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	//Initialize global variables
	//	global.angle = 30.0;
	global.left_click = GLUT_UP;
	global.right_click = GLUT_UP;
	global.view_rotate_x = -45;
	global.view_rotate_y = 30;
	global.win_W = 600;
	global.win_H = 600;

	global.wave_speed = 12;
	global.wave_popularity = 0.4;
	global.wave_height = 1;
	global.water_edge = 30;
	global.step = 0.4;

	global.axis_length = 50;
	global.axis_debug = 1;
	global.normal_debug = 0;
	global.wire_debug = 0;

	global.nearest = 0.01;
	global.farthest = 100;
	global.angle = 30;

	global.ship_direction = create_Vertex(1, 0, 0);
	global.ship_position = create_Vertex(0.0, 10, 0.0);
	global.ship_moving_speed = 0.00;
	//	global.ship_rotation_speed = 0;
	global.ship_angle = 0.0;

	global.scr_W = glutGet(GLUT_SCREEN_WIDTH);
	global.scr_H = glutGet(GLUT_SCREEN_HEIGHT);

}

void set_callbacks(){
	glutDisplayFunc(&displayFunc);
	glutReshapeFunc(&reshapeFunc);
	glutIdleFunc(&idleFunc);
	glutKeyboardFunc(&KeyboardFunc);
	glutSpecialFunc(&SpecialFunc);
	glutMouseFunc(&mouseFunc);
	glutMotionFunc(&motionFunc);
}

void openGL_settings(){
	float lightPosition[] = { 1.0f, 1.0f, 1.0f, 0.0f };
	GLfloat mat_specular[] = { 0.3, 0.3, 0.3, 1.0 };
	GLfloat mat_shininess[] = { 10.0 };

	glClearColor(0, 0, 0, 0);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_NORMALIZE);
	//Lighting
	glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
	glEnable(GL_BLEND);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHT1);
	//Material Color
	glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
	glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
	glEnable(GL_COLOR_MATERIAL);

}

void create_window(){
	unsigned int x = (global.scr_W - global.win_W) / 2;
	unsigned int y = (global.scr_H - global.win_H) / 2;
	glutInitWindowSize(global.win_W, global.win_H);
	glutInitWindowPosition(x, y);
	glutCreateWindow("s3298400 - Assignment 1");
}

void displayFunc(){
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	//Draw
	//Axis
	if(global.axis_debug)
		drawAxes(global.axis_length);
	//Water
	drawWater(global.water_edge);
	//Ship
	drawShip();
	//Display
	gluLookAt(100, 100, 100, 100, 1000, 100, 1, 1, 0);
	glLoadIdentity(); /* Reset matrix */

	// Set Camera View using translation
	glTranslatef(0, 0, -global.farthest / 2);
	//Rotate
	glRotatef(global.view_rotate_y, 1, 0, 0);
	glRotatef(global.view_rotate_x, 0, 1, 0);
	glScalef(0.1, 0.1, 0.1);
	glTranslatef(-global.ship_position.x, global.ship_position.y, -global.ship_position.z); /* "zoom" back 2 units */
	glScalef(10, 10, 10);
	glutSwapBuffers();
	//Redisplay
	glutPostRedisplay();
}

void reshapeFunc(int width, int height){
	//Prevent division by 0
	if(height == 0)
		height = 1;
	global.win_W = width;
	global.win_H = height;
	float aspect = width / (float) height;
	//	glViewport(0, 0, width, height); /*Reset Viewport*/
	glMatrixMode(GL_PROJECTION); /* Change perspective */
	glLoadIdentity(); /* Reset matrix */
	gluPerspective(global.angle, aspect, global.nearest, global.farthest); /* Set up matrix */
	glMatrixMode(GL_MODELVIEW);
	//	glLoadIdentity();

}

void idleFunc(){
	int now_time, elapsed_time;
	static int last_time;
	float wave_speed;
	now_time = glutGet(GLUT_ELAPSED_TIME);
	elapsed_time = now_time - last_time;
	last_time = now_time;
	wave_speed = global.wave_speed / 10000 * ((float) elapsed_time);
	//Wave movement
	global.wave_phi += wave_speed;
	//Ship movement
	global.ship_position.x += global.ship_moving_speed
			* global.ship_direction.x;
	global.ship_position.z += global.ship_moving_speed
			* global.ship_direction.z;
	//	global.ship_rotation_speed += global.ship_angle;
	//	global.ship_angle += 1;

}
void mouseFunc(int button, int state, int x, int y){
	if(button == GLUT_LEFT_BUTTON)
		global.left_click = state;
	if(button == GLUT_RIGHT_BUTTON)
		global.right_click = state;
	global.x_old = x;
	global.y_old = y;
}

void motionFunc(int x, int y){
	if(global.left_click == GLUT_DOWN)
	{
		global.view_rotate_y = global.view_rotate_y + (y - global.y_old) / 5.f;
		global.view_rotate_x = global.view_rotate_x + (x - global.x_old) / 5.f;
		if(global.view_rotate_y > 90)
			global.view_rotate_y = 90;
		if(global.view_rotate_y < -90)
			global.view_rotate_y = -90;
		glutPostRedisplay();
	}
	global.x_old = x;
	global.y_old = y;
}

void SpecialFunc(int key, int x, int y){
	switch(key){
		case GLUT_KEY_UP:
		{
			//Increase moving speed
			global.ship_moving_speed += 0.01;
			break;
		}
		case GLUT_KEY_DOWN:
		{
			//Decrease moving speed
			global.ship_moving_speed = (global.ship_moving_speed < 0) ? 0
					: global.ship_moving_speed - 0.01;
			break;
		}
		case GLUT_KEY_LEFT:
		{
			global.ship_direction.x = cos(M_PI / 360 * (-global.ship_angle));
			global.ship_direction.z = sin(M_PI / 360 * (-global.ship_angle));
			//Rotating is relative to moving
			global.ship_angle += global.ship_moving_speed;
			break;
		}
		case GLUT_KEY_RIGHT:
		{
			//Direction: 1,0,0
			global.ship_direction.x = cos(M_PI / 360 * (-global.ship_angle));
			global.ship_direction.z = sin(M_PI / 360 * (-global.ship_angle));
			//Rotating is relative to moving
			global.ship_angle -= global.ship_moving_speed;
			break;
		}
	}
}

void KeyboardFunc(unsigned char key, int x, int y){
	if(key == 'w' || key == 'W')
	{
		global.wire_debug = (global.wire_debug == 0) ? 1 : 0;
	}
	else if(key == 'a' || key == 'A')
	{
		global.axis_debug = (global.axis_debug == 0) ? 1 : 0;
	}
	else if(key == 'n' || key == 'N')
	{
		global.normal_debug = (global.normal_debug == 0) ? 1 : 0;
	}
	else if(key == '-')
	{
		global.step /= 2;
		if(global.step < 0.1)
			global.step = 0.1;
	}
	else if(key == '+')
	{
		global.step *= 2;
	}
}

void drawAxes(float l){
	/* Axis */
	glBegin(GL_LINES);
	set_color(red);
	glVertex3f(0, 0, 0);
	glVertex3f(l, 0, 0);
	set_color(yellow);
	glVertex3f(0, 0, 0);
	glVertex3f(0, l, 0);
	set_color(green);
	glVertex3f(0, 0, 0);
	glVertex3f(0, 0, l);
	glEnd();
}

void drawShip(){
	OBJMesh* shipMesh = NULL;
	shipMesh = objMeshLoad("galleon.obj");

	//Draw ship
	glPushMatrix();
	/* Scale to 1/10 */
	glScalef(0.1, 0.1, 0.1);
	// Go to the ship's current position
	glTranslatef(global.ship_position.x, global.ship_position.y,
		global.ship_position.z);
	//Rotate ship
	glRotatef(global.ship_angle / 2, 0, 1, 0);
	//Color brown
	//	glMatrixMode(GL_PROJECTION);

	//	gluLookAt(global.ship_position.x + 100, global.ship_position.y + 100,
	//		global.ship_position.z + 100, global.ship_position.x,
	//		global.ship_position.y, global.ship_position.z, 0, 1, 0);
	//	glMatrixMode(GL_MODELVIEW);
	set_color(brown);
	//Draw mesh
	drawMesh(shipMesh);
	free(shipMesh);
	glPopMatrix();
}

void drawWater(float l){
	float x0, x1, y0, y1, y2, y3, z0, z2;
	int pop = 0;
	Vertex p0, p1, p2, p3;
	Vector n;
	set_color(water_color);
	z0 = -l;

	if(global.wire_debug)
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	else
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	for(z0 = -l; z0 < l - global.step; z0 += global.step)
	{

		for(x0 = -l; x0 < l - global.step; x0 += global.step)
		{
			//First vertex
			y0 = calculate_Y(x0, z0);
			//Second vertex
			x1 = x0 + global.step;
			y1 = calculate_Y(x1, z0);
			//Third vertex
			z2 = z0 + global.step;
			y2 = calculate_Y(x0, z2);
			//Forth vertex
			y3 = calculate_Y(x1, z2);

			//Prepare for normals calculation
			p0 = create_Vertex(x0, y0, z0);
			p1 = create_Vertex(x1, y1, z0);
			p2 = create_Vertex(x0, y2, z2);
			p3 = create_Vertex(x1, y3, z2);

			n = crossProduct2v(create_Vector(p0, p1), create_Vector(p2, p1));

			//Normal Debug
			if(global.normal_debug)
			{
				if(pop == 1)
				{
					set_color(yellow);
					glBegin(GL_LINE);
					glVertex3f(p0.x, p0.y, p0.z);
					glVertex3f((n.x + p0.x) * 1.1, (n.y + p0.y) * 1.1, (n.z
							+ p0.z) * 1.1);
					glEnd();
					pop = 0;
				}
				else
				{
					pop = 1;
				}
			}
			glBegin(GL_TRIANGLES);
			set_color(water_color);
			glNormal3f(n.x, n.y, n.z);
			glVertex3f(p0.x, p0.y, p0.z);
			glNormal3f(n.x, n.y, n.z);
			glVertex3f(p1.x, p1.y, p1.z);
			glNormal3f(n.x, n.y, n.z);
			glVertex3f(p2.x, p2.y, p2.z);
			glEnd();
			n = crossProduct2v(create_Vector(p1, p3), create_Vector(p2, p3));
			glBegin(GL_TRIANGLES);
			glNormal3f(n.x, n.y, n.z);
			glVertex3f(p2.x, p2.y, p2.z);
			glNormal3f(n.x, n.y, n.z);
			glVertex3f(p1.x, p1.y, p1.z);
			glNormal3f(n.x, n.y, n.z);
			glVertex3f(p3.x, p3.y, p3.z);
			glEnd();
		}
	}
}

void drawMesh(OBJMesh* mesh){
	glBegin(GL_TRIANGLES);
	for(int i = 0; i < mesh->numIndices; ++i)
	{
		unsigned int index = mesh->indices[i];
		float* vert = (float*) ((void*) mesh->vertices + index * mesh->stride);
		float* norm = (float*) ((void*) vert + mesh->normalOffset);
		if(mesh->hasNormals)
			glNormal3fv(norm);
		glVertex3fv(vert);
	}
	glEnd();
}

Vector crossProduct3v(Vector t, Vector u, Vector v){
	Vector a = { t.x - u.x, t.y - u.y, t.z - u.z };
	Vector b = { u.x - v.x, u.y - v.y, u.z - v.z };
	return crossProduct2v(a, b);
}
Vector crossProduct2v(Vector v1, Vector v2){
	Vector v = { v1.y * v2.z - v2.y * v1.z, v1.z * v2.x - v1.x * v2.z, v1.x
			* v2.y - v1.y * v2.x };
	return v;
}

Vertex create_Vertex(float x, float y, float z){
	Vertex v = { x, y, z };
	return v;
}
Vector create_Vector(Vertex v1, Vertex v2){
	Vector v = { v2.x - v1.x, v2.y - v1.y, v2.z - v1.z };
	return v;
}
float calculate_Y(float x, float z){
	return sin(x * global.wave_popularity + global.wave_phi) * sin(z
			* global.wave_popularity + global.wave_phi) * global.wave_height;
}
void set_color(Color color){
	glColor3f(color.R, color.G, color.B);
}
Color create_Color(float r, float g, float b){
	Color c = { r, g, b };
	return c;
}
float len_Vector2D(Vector v){
	float d = sqrt(v.x * v.x - v.z * v.z);
	return (v.z > 0) ? d : -d;
}
